iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
Software Development

從 C++ 菜鳥到 Low-Latency 勇者:一場分秒必爭的賽局系列 第 20

[Day 20] High-Performance Concurrency: Linux CPU Scheduling

  • 分享至 

  • xImage
  •  

前兩篇文篇討論了 thread pinning 的基本原理:透過 CPU affinity 限制 Thread 的執行範圍,降低 CPU migration 與其他 scheduling interference,進一步改善 latency predictability。

然而真正進入 production environment 後,遠比單純使用 pthread_setaffinity_np() 等 API 複雜得多。因為即使我們成功把 thread 綁定到某個特定的 CPU,也無法保證此 CPU 是乾淨的。Linux kernel、interrupt、softirq、kernel worker、SMT sibling,以及 NUMA memory placement 都可能影響最後結果。

因此,本篇的重點是建立一套完整的方法,用 Linux 嘗試進行實戰演練。

  • 配置與路徑隔離:CPU affinity、CPU isolation、IRQ placement。
  • 底層拓撲理解:CPU Topology、NUMA Locality。
  • 效能與延遲驗證:Benchmark、Tail latency analysis。

1. 最基本的 CPU Affinity —— taskset

Linux 提供的 taskset 是最容易開始測試 CPU affinity 的工具,我們可以使用這些指令來限制應用程式所使用的 CPU 核心:

# 限制應用程式僅在單一 CPU 6 執行
taskset -c 6 ./my_application
# 查看特定 process 的 affinity 配置
taskset -cp 12345
# 預期回應 (代表 process 僅能在 CPU 6 執行):
# pid 12345's current affinity list: 6
# 啟動應用程式並綁定多個 CPU
taskset -c 4,6,7 ./my_application
# 此時 scheduler 僅能在允許的 CPU 之間輪替選擇:allowed CPUs = {4,6,7}

2. Application 內部 Pin Thread

Process-level affinity 有時候還不夠。若想落實更細緻的 thread 資源分配,可以在 thread 建立後設定 affinity。

Application Threads
⠀├── Main Thread
⠀├── Network Threadㅤ⠀→ CPU 4
⠀├── Worker 1ㅤㅤㅤㅤ⠀→ CPU 6
⠀├── Worker 2ㅤㅤㅤㅤ⠀→ CPU 7
⠀└── Logging Threadㅤㅤ→ CPU 1

Linux pthread 常見做法是:

cpu_set_t cpuset;

CPU_ZERO(&cpuset);
CPU_SET(6, &cpuset);

pthread_setaffinity_np(
    pthread_self(),
    sizeof(cpuset),
    &cpuset
);

如果需要更完整的錯誤處理,production code 應該檢查 API return value,並確認指定的 CPU 在目前系統 topology 中有效。這種方式最大的優點是可以把 CPU placement 直接寫進 application architecture,對 pipeline-style workload 特別有用。

理想的架構映射範例:
RX Thread → CPU 4
Decode Thread → CPU 6
Encode Thread → CPU 7

3. CPU Isolation —— 從「我去哪裡」變成「別人不要來」

如果只使用 affinity 將 critical thread 綁定到 CPU 6,該 CPU 依然可能被系統排程器分配其他一般行程,或因處理硬體中斷與 kernel threads 而受到干擾。因此,在 latency-sensitive system 中,可以進一步考慮 CPU isolation。概念上類似於下圖:

CPU 0-3⠀housekeeping⠀⠀一般系統工作則盡可能留在這

CPU 4-7⠀isolated⠀ ⠀⠀⠀⠀可負責 application 的 critical threads
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Thread A → CPU 4
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Thread B → CPU 6
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Thread C → CPU 7

現代 Linux 提供 isolcpusnohz_full 等多種 CPU isolation 與 scheduler tuning 機制。實際可用參數會隨 kernel 版本與 distribution 而有所不同,因此不應把某一組 boot parameter 視為所有系統通用的固定答案。

核心原則比較重要:先理解 topology,再決定 isolation strategy。

4. IRQ Affinity —— 常被忽略的干擾來源

假設 CPU 6 是 critical CPU,但某張高速網卡仍然把大量 interrupts 丟到 CPU 6:

Application Thread → CPU 6
NIC
⠀├── IRQ → CPU 6
⠀├── IRQ → CPU 6
⠀└── IRQ → CPU 6

那麼 application 再怎麼 pin,都可能遇到 latency spikes。所以 high-performance networking system 通常還需要處理 IRQ affinity。

Linux 可以透過 /proc/irq/ 下的 affinity 設定來控制特定 interrupt 的 CPU placement。例如查看:
cat /proc/irq/<IRQ>/smp_affinity_list

實際部署時,可以把 network IRQ 分配到 housekeeping CPUs,而讓 application critical CPUs 儘量減少不必要的 interrupt activity,這樣才能真正形成 separation:
NIC IRQ → CPU 0 / CPU 1
Application RX → CPU 6

5. 不要忘記 SoftIRQ

Networking workload 特別容易踩到這個問題。硬體 interrupt 並不是全部工作,Linux network stack 還涉及 softirq processing。如果 CPU isolation 只處理 application thread,而沒有考慮 kernel networking activity,結果可能與預期不同。這也是為什麼高效能網路系統通常需要從整條路徑一起分析,而不是只看 application thread。

NIC Interrupt (Network Interface Card)
  ↓
Hard IRQ (Top Half)
  ↓ ─ [Kernel Networking Activity]
SoftIRQ (Bottom Half)
  ↓
Network Stack (IP/TCP/UDP Processing)
  ↓
Application (Critical Thread)

6. NUMA —— Thread 與 Memory 要一起配置

在 dual-socket 或 multi-socket server 上,CPU pinning 後下一個問題通常就是 memory locality。可以使用 lscpu 查看 CPU topology,也可以用 numactl --hardware 查看 NUMA node 與 memory topology。

如同 Day 19 提到的觀念,如果 critical thread 在 CPU 0-15,那麼通常也希望它主要使用 NUMA node 0 memory。測試時可以使用以下指令,讓 CPU 與 memory placement 更一致:
numactl --cpunodebind=0 --membind=0 ./my_application

但同樣要注意:NUMA policy 不應盲目套用。實際最佳配置取決於 application 的 memory access pattern。

7. 如何確認 Thread 真的被 Pin 住?

不能只相信 configuration,關鍵在於這三者之間的驗證:

┌────────────┐
│   Configuration   │ NUMA / CPU affinity 設定
└─────┬──────┘
      │── 檢驗配置邊界:taskset -cp <PID>
┌─────┴──────┐
│ Observed CPU Placement │ 透過工具觀察到的動態實際位置
└─────┬──────┘
      │── 查看核心分配:ps -eLo pid,tid,psr,comm
┌─────┴──────┐
│  Measured Latency  │ 最後量測出來的效能與延遲結果
└────────────┘

其中 PSR 可以用來觀察 thread 最近執行所在的 processor,更進一步可以使用 perf 或其他 tracing/profiling 工具觀察 scheduler behavior。


上一篇
[Day 19] High-Performance Concurrency: Thread Pinning II
下一篇
[Day 21] 進度存檔 | Low-Latency 中繼城冒險日誌
系列文
從 C++ 菜鳥到 Low-Latency 勇者:一場分秒必爭的賽局21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言